package com.mytx.zuul.filter;

import com.alibaba.fastjson.JSONObject;
import com.mytx.common.constant.Constant;
import com.mytx.common.exception.DecodeException;
import com.mytx.common.exception.RequestExpireException;
import com.mytx.common.exception.TokenException;
import com.mytx.common.utils.DateUtils;
import com.mytx.common.utils.HttpEncryptUtil;
import com.mytx.common.utils.JwtTokenUtils;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import io.jsonwebtoken.*;
import io.netty.buffer.ByteBufAllocator;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.core.io.buffer.DataBuffer;

import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;


import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@Component
@SuppressWarnings("Duplicates")
public class DecodeFilter extends ZuulFilter {
    @Autowired
    private RedisTemplate redisTemplate;
    private Logger logger = LoggerFactory.getLogger(DecodeFilter.class);
    private static final String servicePrivatekey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCHXLqEITtVT9SsEc7oGZl5VZWsbDliXNUQW9tx8p9Ky3U/S3DkQrqnu6VxY9LL/n20LUxt2LKJn37fydGifYpXIi5Gcpdvuu0inZzlGuIRkdogDyqaPvl3g9im2yxZXQswtyz58vHTRvhcMjpD2Vr/X74WH+c/iv1DAjK7ZC9cIB3dJpDHGvVB0vlkcPNWaFXsHbh09CIr1NvvEiqsUysXusCvgF7zwpvdyRqUphQfWGfftkVUb6+N3/sJMG6656g2OknsqVeUGJvjpeneMGW99tp4pQ31EWX55skvmRY8fcu1QPxCMp0Ia4KgfbRG8IosjhPUCqBapNmEn+vMJJ6FAgMBAAECggEAYrixWy41bLnlPH1cbpBFC2Xs3CyuCVv1FJI50LgHjvAmGbx2oDM+mJhVSAZqtdZsbVzgMfjp9U86pBI37w8qNvQ51a07XwRwcF+NDtZ1bCT4dYs6Ifat0Il7F+4fQSmHwMbHaZ/S2KkY93y2W8eJA1kbdF1eNUyz+wv+XgbqN4Auqo7Dz2IpLkvYUuTPGnE3OuhrKK7qADKEEehuad5YZu1sr4nS2sdf9AE/Oin6TRHF50oAwwa82F886BmuVgj5H2hHkh5WM8XIj/KVbo2xvHVf+1aUhSOVmkPgq1R55o/AxGHtXH5lwuLEwiEEDKWej3WxMOkQAvLKvmDr5Yp9PQKBgQDWXK9g50fOPuv9ilvWSPVZ6D2+A9lFDt+3FDau/j0GSpSJJZmoyhLgVK7EDsaFIovNZ/+tI2vxpjwr7AqqLRt+IMrRR/Zzj6psOaVMH/KOsgR9Da3UiRoHgcwLenHEoc2U3UX1bRb9pmmpH5ujgHCAadvmIa2yr1CnLp/G6cFOywKBgQChp7efdcmE8TCdU1SekdITixawduOWF5furUZ2OzvrBl+hO99cbkjgrsEfQfgQpniYTFHPh4LXC5Vcyd6ZuT5dJUl4fcLjJig5ekkAEqrSce9PRNP+TyqmCr+tz1lTjFuKKn8bTP9frr60G8eD1EAwNfHaMZZzvB73E4+1s8tN7wKBgHzYZEgiyz5ChT0YDN6jhUnEqdj18t92hzLgcey/I5l7bEDoBjM45X+Dsnl7RYGamiOac11KKP8HWzoVciykssNlo9n5eH7baF05wZyxappS8axHgDstUlyW6tiwinwD5HDT4n/K9j3qYtVeFE5Iar1PagQBD2HnGykTTXkTAUO7AoGAaTV7t4d0YVaMGkhqsU0cWWXTnYOy+TNfyZHyYu6IP50FTi1mYITSX5C81fgcV9HQeOZq9FMwn37Fdf9YJdo/MWYcHBRVa3qhAU+KBUcg8BgA9SlJg6DIlAo42Ak0DMEzroeb9Au3xPE44oWV40GXiQkC38wYLDjKOmn7xPlAHvcCgYEAmOX9PWA7RdgzcTKyH2kHOu/pgMwT8QarpRaO1F9HpjnC3cdcCkPPE4F/R6VKy4Zfca1yiOzK+bxxBlJxmLQQywuaFRmWXNytiqbU7zwBXI6SJeo5EvrU8PSif4ctOpQzyhxYFTDgtyauqrW0ws4ZCqlt+9zGBaiIqTDLxUWexO8=";

//    @Override
//    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//        return chain.filter(exchange);
//        ServerHttpRequest request = exchange.getRequest();
//
//
//        //如果是get请求，直接放行
//        if (StringUtils.equals(request.getMethodValue(), "GET")) {
//            return chain.filter(exchange);
//
//        }
//
//        //默认是加密请求，如果header中没有Encrypt，说明这个请求是加密的。有的 Encrypt,反而是不加密的，也许这样有点反人类，以后再考虑是否修改，，，
//        HttpHeaders httpHeaders = request.getHeaders();
//        if (httpHeaders.containsKey("Encrypt")) {
//            return chain.filter(exchange);
//        }
//
//
//        Flux<DataBuffer> body = request.getBody();
//        AtomicReference<String> bodyRef = new AtomicReference<>();
//        body.subscribe(dataBuffer -> {
//            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer());
//            DataBufferUtils.release(dataBuffer);
//            bodyRef.set(charBuffer.toString());
//
//        });
//        String bodyStr = bodyRef.get();//这样获取到请求body
//        if (StringUtils.isEmpty(bodyStr)) {
//            logger.info("body是空的,跳过解密步骤");
//            return chain.filter(exchange);
//        }
//
//
//        JSONObject decrypt = new JSONObject();
//        try {
//            decrypt = HttpEncryptUtil.serverDecrypt(bodyStr, servicePrivatekey);
//        } catch (Exception e) {
//            e.printStackTrace();
//            logger.error("解密错误" + bodyStr);
//            throw new DecodeException("解密错误");
//        }
//        logger.info("解密后的body：" + decrypt.toJSONString());
//
//        //检查是否超时
//        Date created = decrypt.getDate("created");
//        //如果客户端的请求创建时间在5秒钟之前，我们认为此请求已经过期，
//        if (created.before(new Date(System.currentTimeMillis() - 3600 * 1000))) {
//            logger.error("请求过期" + decrypt);
//            throw new RequestExpireException("请求过期");
//        }
//
//        //获取token
////        String token = decrypt.getString("token");
////        if (StringUtils.isEmpty(token) && request.getURI().getPath().equals("restLogin")) {
////            return chain.filter(exchange);
////        }
//
//
//        Flux<DataBuffer> bodyFlux = Flux.just(stringBuffer(decrypt.getString("ct")));
//        //在解密后的json获取ak
//        String ak = decrypt.getString("ak");
//        //把解密获取的ak,通过下面的包装类，添加进header,,把解密后的body,也放进包装类
////        MyServerHttpRequestDecorator requestDecorator = new MyServerHttpRequestDecorator(request);
////        requestDecorator.addHeader("AK", ak);
////        requestDecorator.setBufferFlux(bodyFlux);
//
//        request = new ServerHttpRequestDecorator(request){
//            @Override
//            public Flux<DataBuffer> getBody() {
//                return bodyFlux;
//            }
//        };//封装我们的request
//
//       // return chain.filter(exchange.mutate().request(request).build());
//        return chain.filter(exchange);
    // }

    protected DataBuffer stringBuffer(String value) {
        byte[] bytes = value.getBytes(StandardCharsets.UTF_8);

        NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
        DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
        buffer.write(bytes);
        return buffer;
    }


    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
//如果是get请求，直接放行
        if (StringUtils.equals(request.getMethod(), "GET")) {
            ctx.setRequest(request);
            return false;
        }

        //默认是加密请求，如果header中没有Encrypt，说明这个请求是加密的。有的 Encrypt,反而是不加密的，也许这样有点反人类，以后再考虑是否修改，，，
        String encrypt = request.getHeader("Encrypt");
        if (StringUtils.isNotEmpty(encrypt)) {
            ctx.setRequest(request);
            return false;
        }
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();


        ServletInputStream inputStream = null;
        String content = null;
        try {
            inputStream = request.getInputStream();
            content = StreamUtils.copyToString(inputStream, Charset.forName("UTF-8"));
        } catch (IOException e) {

        }


        if (StringUtils.isEmpty(content)) {
            logger.info("body为空，跳过解密步骤：" + request.getRequestURL());
            ctx.setRequest(request);
            return null;
        }

        JSONObject decrypt;
        try {
            //对加密请求body进行解密
            decrypt = HttpEncryptUtil.serverDecrypt(content, servicePrivatekey);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("解密错误" + content);
            throw new DecodeException("解密错误");
        }
        // logger.info("解密后的body：" + decrypt.toString());
        Date created = decrypt.getDate("created");
        logger.info("created:{}",created);
        //如果客户端的请求创建时间在5秒钟之前，我们认为此请求已经过期，
       long time =  System.currentTimeMillis();
        Date date = new Date(time);
        logger.info("date:{}",date);
        if (created.before(new Date(time - 10* 1000))) {
            logger.error("请求过期" + decrypt);
            throw new RequestExpireException("请求过期");
        }
        //生成request的包装类，因为原生的HttpServletRequest，不支持对body的修改，等于说body,只能读取一次
        MyHttpServletRequestWrapper wrapper = new MyHttpServletRequestWrapper(request);


        //注册  登录 发送注册验证 发送找回密码验证码 ,授权登录不需要携带token
        if (!decrypt.containsKey("token") && !StringUtils.equalsAny(request.getServletPath(), "/user/getSts","/user/register", "/user/login_v1", "/user/sendRegisterMobileCode", "/user/sendMobileCode_1","/user/getAuthUrl","/user/aliAuthLogin","/user/userBindAccount")) {
            throw new TokenException("没有token");
        }

        String token = decrypt.getString("token");
        String uId = null;
        if (StringUtils.isNotBlank(token)) {
            uId = getAuthentication(token, ctx.getResponse());
        }

        ctx.addZuulRequestHeader("AK", decrypt.getString("ak"));
        //这儿需要做判断，有可能 body里面只有token,比如autoLoin的请求，就不含有content，
        if (decrypt.containsKey("ct")) {
            //把解密后的body，放入包装类
            wrapper.setRequestBody(decrypt.getString("ct"));
        }
        ctx.addZuulRequestHeader("uId", uId);
        logger.info("uId:{} servletPath:{}", uId, request.getServletPath());
        ctx.setRequest(wrapper);


        return null;
    }


    private String getAuthentication(String token, HttpServletResponse response) {


        Claims claims;
        try {
            //logger.info("token:" + token);

            claims = Jwts.parser().setSigningKey("#$%$TFDGTRYU%^&#$%TERYHFRTY").parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException e) {
            logger.error("Token已过期: {} " + e);
            throw new TokenException("Token已过期");
        } catch (UnsupportedJwtException e) {
            logger.error("Token格式错误: {} " + e);
            throw new TokenException("Token格式错误");
        } catch (MalformedJwtException e) {
            logger.error("Token没有被正确构造: {} " + e);
            throw new TokenException("Token没有被正确构造");
        } catch (SignatureException e) {
            logger.error("签名失败: {} " + e);
            throw new TokenException("签名失败");
        } catch (IllegalArgumentException e) {
            logger.error("非法参数异常: {} " + e);
            throw new TokenException("非法参数异常");
        }
        //  logger.info("token解密：" + claims.toString());
        String tokenSalt =(String)claims.get("salt");
        String uId = (String) claims.get("u_id");
        Date currentDate = new Date();
        Date expiration = claims.getExpiration();
        //判断是否过期
        if (expiration.before(currentDate)) {
            logger.error("登录:Token已过期-" + uId);
            throw new TokenException("token无效");
        }

        String redisSalt = null;
        try {
            Object saltObject = redisTemplate.opsForHash().get(Constant.USER_SESSION + uId, "salt");
            if (saltObject != null) {
                redisSalt = (String) saltObject;
            }
        } catch (Exception e) {
            logger.error("登录:" + e.getMessage());
            throw new TokenException("token无效");
        }

        //sqlt匹配不上，说明用户已经在别的地方已经登录过了，需要重新登录
        if (!StringUtils.equals(redisSalt,tokenSalt)) {
            logger.error("登录:salt不匹配uid:{},redisSalt:{},tokenSalt:{}", uId, redisSalt, tokenSalt);
            throw new TokenException("token无效");
        }

        // 判断token 是否在 7天内到期，如果是，更新token
        if (expiration.before(DateUtils.addDay(currentDate, 7))) {
            logger.info("token有效期在7天内，重建token:" + uId);
            String newToken = JwtTokenUtils.genToken(redisSalt, uId);
            //在响应中加入新token
            response.setHeader("token", newToken);
            //重新设置用户信息缓存有效期为30天
            redisTemplate.expire(Constant.USER_SESSION + uId, Constant.LOGIN_EXPIRE_TIME, TimeUnit.DAYS);

        }

        return uId;
    }

}
